#include <fstream>
#include <dlfcn.h>
#include <dirent.h>

#include "evdeploy/utils/json/json.h"
#include "evdeploy/core/ev_model_adapter.h"

namespace ev
{

    static const std::vector<std::string> g_model_config_element{"r_model_type", "lib", "r_model_name", "r_desc", "r_id", "r_engine", "r_frame_work"};

    static std::map<std::string, std::string> MakeDefaultResponseKeyValue()
    {
        std::map<std::string, std::string> ret;
        for (const auto &config_element : g_model_config_element)
        {
            ret[config_element] = "----";
        }
        return ret;
    }

    static EVStatus FreeInquiryResponse(const char *resp)
    {
        if (resp != NULL)
        {
            free((void *)resp);
            return EV_SUCCESS;
        }
        return EV_INVALID_ARG;
    }

    static Value MakeInquiryResponseJson(const std::map<std::string, std::string> kvs)
    {
        Value jvalue_item;
        for (auto iter = kvs.begin(); iter != kvs.end(); ++iter)
        {
            if (iter->first.size() > 2)
            {
                jvalue_item[iter->first] = iter->second;
            }
        }
        return jvalue_item;
    }

    static const char *MakeInquiryResponse(std::vector<Value> models)
    {
        Value inquiry_response;
        inquiry_response.resize(0);
        for (const auto &model : models)
        {
            inquiry_response.append(model);
        }
        std::string str_inquiry_response = inquiry_response.toStyledString();
        char *ret_str = (char *)malloc(str_inquiry_response.size() + 1);
        memset(ret_str, 0, str_inquiry_response.size() + 1);
        memcpy(ret_str, str_inquiry_response.c_str(), str_inquiry_response.size());
        return ret_str;
    }

    static bool CheckModelConfig(const std::string &model_config, std::map<std::string, std::string> &kvs)
    {
        std::ifstream f(model_config);
        Reader j_reader;
        Value config;
        if (j_reader.parse(f, config))
        {
            for (const auto &element : g_model_config_element)
            {
                std::string value;
                value = config[element].asString();
                kvs[element] = value;
            }
        }
        return true;
    }

    static bool CheckModelLib(const std::string &lib_path)
    {
        if (lib_path.size() > 7 && lib_path.substr(lib_path.size() - 3) == ".so")
        {
            auto handle = dlopen(lib_path.c_str(), RTLD_LAZY);
            if (handle == NULL)
            {
                EVLOG(ERROR) << "fail to open lib " << lib_path << " " << dlerror();
                exit(-1);
            }

            auto *func1 = dlsym(handle, "create_infer_ins");
            if (func1 == NULL)
            {
                dlclose(handle);
                EVLOG(ERROR) << "fail to parse symbol  create_infer_ins" << dlerror();
                exit(-1);
            }

            auto *func2 = dlsym(handle, "destroy_infer_ins");
            if (func2 == NULL)
            {
                dlclose(handle);
                EVLOG(ERROR) << "fail to parse symbol destroy_infer_ins" << dlerror();
                exit(-1);
            }
            dlclose(handle);
            return true;
        }
        else
        {
            return false;
        }
    }

    EVModelAdapter::EVModelAdapter()
    {
    }

    EVModelAdapter::~EVModelAdapter()
    {
        for (auto iter = m_config_to_model_factory.begin(); iter != m_config_to_model_factory.end(); iter++)
        {
            delete iter->second;
        }
    }

    void EVModelAdapter::Init(const std::string &str)
    {
        std::ifstream config_file(str);
        Value config;
        Reader j_reader;
        j_reader.parse(config_file, config);

        if (config_file.is_open())
        {
            config_file.seekg(0, std::ios::end);
            size_t len = config_file.tellg();
            config_file.seekg(0, std::ios::beg);
            char *conf_str = new char[len + 1];
            config_file.read(conf_str, len);
            conf_str[len] = '\0';
            EVLOG(INFO) << "EVDeploy config:\n" << conf_str;
            delete[] conf_str;
            config_file.close();
        }
        Value model_lib_paths = config["model_lib_path"];
        std::vector<std::string> libs_in_folder{};
        for (int i = 0; i < model_lib_paths.size(); ++i)
        {
            std::string lib_path;
            lib_path = model_lib_paths[i].asString();
            if (CheckFileType(lib_path.c_str()) == EV_FOLDER)
            {
                DIR *pDir = NULL;
                struct dirent *ptr = NULL;
                pDir = opendir(lib_path.c_str());
                if (lib_path.back() != '/')
                {
                    lib_path = lib_path + "/";
                }

                while (pDir != NULL && (ptr = readdir(pDir)) != 0)
                {
                    if (CheckFileType((lib_path + ptr->d_name).c_str()) == EV_FILE)
                    {
                        std::string lib = lib_path + ptr->d_name;

                        if (lib.substr(lib.size() - 3) == ".so" && lib.find("evdeploy.so") == std::string::npos && lib.find("evauth.so") == std::string::npos)
                        {
                            libs_in_folder.push_back(lib);
                            EVLOG(INFO) << lib;
                        }
                    }
                }
                if (pDir != NULL)
                {
                    closedir(pDir);
                }
            }
            else if (CheckFileType(lib_path.c_str()) == EV_FILE)
            {
                if (lib_path.substr(lib_path.size() - 3) == ".so" && lib_path.find("evdeploy.so") == std::string::npos && lib_path.find("evauth.so") == std::string::npos)
                {
                    libs_in_folder.push_back(lib_path);
                }
            }
        }
        for (const auto &lib_name : libs_in_folder)
        {
            if (CheckModelLib(lib_name))
            {
                EVBaseModelFactory *ev_base_model_factory = new EVBaseModelFactory(lib_name);
                m_config_to_model_factory[lib_name.substr(lib_name.find_last_of("/") + 4)] = ev_base_model_factory;
            }
        }
#ifdef ATLAS
        aclInit("/usr/local/evdeploy/configs/acl.json");
#endif
    }

    EVModelAdapter &EVModelAdapter::GetAdapter()
    {
        static EVModelAdapter s_adapter;

        return s_adapter;
    }

    bool EVModelAdapter::InitModel(const std::string &config_file)
    {
        Init(config_file);
        if (CheckFileType(config_file.c_str()) == EV_NOT_EXIST)
        {
            EVLOG(ERROR) << "config file not exists : " << config_file;
            exit(-1);
        }
        std::ifstream f(config_file);
        Value config;
        Reader j_reader;
        if (!j_reader.parse(f, config))
        {
            EVLOG(ERROR) << "config file invalid : " << config_file;
            exit(-1);
        }

        config = config["serving_models"];
        std::string server_address, type, mode, engine, framework, version, model, uuid;
        for (int i = 0; i < config.size(); ++i)
        {
            Value model_config = config[i];
            uuid = model_config["uuid"].asString();
            if (m_model_config.find(uuid) != m_model_config.end())
            {
                EVLOG(WARNING) << "you configure different model with the same uuid, this may cause unexpected error " << uuid;
                continue;
            }
            server_address = model_config["server_address"].asString();
            mode = model_config["mode"].asString();
            type = model_config["type"].asString();
            engine = model_config["engine"].asString();
            framework = model_config["framework"].asString();
            version = model_config["version"].asString();
            model = model_config["model"].asString();
            std::vector<std::string> inputs{};
            std::vector<std::string> outputs{};

            bool valid_input = model_config.isMember("inputs") && (model_config["inputs"].size() > 0);
            if (!valid_input)
            {
                EVLOG(ERROR) << "please configure inputs for commont tensorrt model";
                exit(-1);
            }
            for (int k = 0; k < model_config["inputs"].size(); ++k)
            {
                inputs.push_back(model_config["inputs"][k].asString());
            }

            bool valid_output = model_config.isMember("outputs") && (model_config["outputs"].size() > 0);
            if (!valid_input)
            {
                EVLOG(ERROR) << "please configure outputs for commont tensorrt model";
                exit(-1);
            }
            for (int k = 0; k < model_config["outputs"].size(); ++k)
            {
                outputs.push_back(model_config["outputs"][k].asString());
            }
            EVLOG(INFO) << "add model config :" << uuid;
            m_model_config[uuid] = {server_address, mode, type, engine, framework, version, model, inputs, outputs};
        }
        return true;
    }

    bool EVModelAdapter::CreateModel(const std::string &uuid)
    {
        EVLOG(INFO) << "start create model:" << uuid;
        m_locker.lock();
        if (m_model_config.find(uuid) == m_model_config.end())
        {
            EVLOG(ERROR) << "unknown model" << uuid;
        }
        std::string server_address = m_model_config[uuid].server_address;
        std::string mode = m_model_config[uuid].mode;
        std::string type = m_model_config[uuid].type;
        std::string engine = m_model_config[uuid].engine;
        std::string framework = m_model_config[uuid].framework;
        std::string version = m_model_config[uuid].version;
        std::string model = m_model_config[uuid].model;
        if (mode != "local_model")
        {
            EVLOG(ERROR) << "unknow model mode : " << mode;
            return false;
        }

        std::string key;
        key = engine + ".so";
        if (m_config_to_model_factory.find(key) == m_config_to_model_factory.end())
        {
            EVLOG(ERROR) << "fail to create model, may not exist the so: "
                         << "lib" + key << " in the given evdeploy config path, please check";
            m_locker.unlock();
            exit(-1);
        }

        EVBaseModel *base_model = (EVBaseModel *)m_config_to_model_factory[key]->CreateProduct("");

        m_local_models[uuid] = base_model;
        m_product_to_model_name[base_model] = model;

        void **config = nullptr;
#ifdef ATLAS
        aclrtContext *context = new aclrtContext;
        auto ret = aclrtCreateContext(context, 0);
        void *config1[1] = {context};
        config = config1;
#endif

        SetIO(base_model, m_model_config[uuid].inputs, m_model_config[uuid].outputs);
        base_model->Init(m_product_to_model_name[base_model].c_str(), config);
        m_product_to_model_factory[base_model] = m_config_to_model_factory[key];

        m_locker.unlock();

        EVLOG(INFO) << "using model:" << model;
        EVLOG(INFO) << "end create model";

        return true;
    }

    bool EVModelAdapter::RunInfer(const std::string &uuid, EVModelData *inputs, EVModelData *outputs)
    {
        EVLOG(INFO) << "start run infer";
        if (m_local_models.find(uuid) != m_local_models.end())
        {
            return m_local_models.find(uuid)->second->Infer(inputs, outputs);
        }
        else
        {
            EVLOG(ERROR) << "failed to find model : " << uuid;
            exit(-1);
        }
        EVLOG(INFO) << "end run infer";
        return true;
    }

    bool EVModelAdapter::DestroyModel(const std::string &uuid)
    {
        EVLOG(INFO) << "start destroy model";
        m_locker.lock();
        if (m_local_models.find(uuid) != m_local_models.end())
        {
            m_product_to_model_factory[m_local_models.find(uuid)->second]->DestroyProduct(m_local_models.find(uuid)->second);
            m_local_models.erase(uuid);
        }
        m_locker.unlock();
        EVLOG(INFO) << "end destroy model";
        return true;
    }

    bool EVModelAdapter::SetIO(EVBaseModel *p, const std::vector<std::string> &inputs, const std::vector<std::string> &outputs)
    {
        p->SetIO(inputs, outputs);
        return true;
    }

} // end namespace ev
